6.1 定义

方法是与对象实例绑定的特殊函数。

方法是面向对象编程的基本概念,用于维护和展示对象的自身状态。对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来暴露对外通信接口。普通函数则专注于算法流程,通过接收参数来完成特定逻辑运算,并返回最终结果。换句话说,方法是有关联状态的,而函数通常没有。

方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。在某些语言里,尽管没有显式定义,但会在调用时隐式传递this实例参数。

可以为当前包,以及除接口和指针以外的任何类型定义方法。

type N int
  
func(n N)toString()string{ 
   return fmt.Sprintf("%#x",n) 
} 
  
func main() { 
   var a N=25
   println(a.toString()) 
}

输出:

0x19

方法同样不支持重载(overload)。receiver参数名没有限制,按惯例会选用简短有意义的名称(不推荐使用this、self)。如方法内部并不引用实例,可省略参数名,仅保留类型。

type N int
  
func(N)test() { 
   println("hi!") 
}   

方法可看作特殊的函数,那么receiver的类型自然可以是基础类型或指针类型。这会关系到调用时对象实例是否被复制。

type N int
  
func(n N)value() {                //func value(n N) 
   n++ 
   fmt.Printf("v: %p, %v\n", &n,n) 
} 
  
func(n*N)pointer() {              //func pointer(n*N) 
    (*n)++ 
   fmt.Printf("p: %p, %v\n",n, *n) 
} 
  
func main() { 
   var a N=25
  
   a.value() 
   a.pointer() 
  
   fmt.Printf("a: %p, %v\n", &a,a) 
}
 

输出:

v:0xc8200741c8,26            //receiver被复制 
p:0xc8200741c0,26
a:0xc8200741c0,26

可使用实例值或指针调用方法,编译器会根据方法receiver类型自动在基础类型和指针类型间转换。

func main() { 
   var a N=25
   p:= &a
  
   a.value() 
   a.pointer() 
  
   p.value() 
   p.pointer() 
}
 

输出:

v:0xc82000a2c0,26
p:0xc82000a298,26
  
v:0xc82000a2f0,27
p:0xc82000a298,27

不能用多级指针调用方法。

func main() { 
   var a N=25
  
   p:= &a
   p2:= &p
  
   p2.value()      // 错误:calling method value with receiver p2(type**N) 
         //         requires explicit dereference
  
   p2.pointer()     // 错误:calling method pointer with receiver p2(type**N) 
         //         requires explicit dereference
}

指针类型的receiver必须是合法指针(包括nil),或能获取实例地址。

type X struct{} 
  
func(x*X)test() { 
   println("hi!",x) 
} 
  
func main() { 
   var a*X
   a.test()            // 相当于test(nil) 
  
   X{}.test()       // 错误:cannot take the address of X literal
}

将方法看作普通函数,就能很容易理解receiver的传参方式。

如何选择方法的receiver类型?

  • 要修改实例状态,用*\T。
  • 无须修改状态的小对象或固定值,建议用T。
  • 大对象建议用*\T,以减少复制成本。
  • 引用类型、字符串、函数等指针包装对象,直接用T。
  • 若包含Mutex等同步字段,用*\T,避免因复制造成锁操作无效。
  • 其他无法确定的情况,都用*\T。